package com.iakuil.app.common.core;

import com.iakuil.app.common.core.annotation.Unique;
import com.iakuil.app.common.core.domain.BaseEntity;
import org.springframework.lang.Nullable;

import java.util.Collection;
import java.util.List;

/**
 * 通用Domain Service（基于MyBatis Plus实现）
 *
 * <p>提供<strong>单表</strong>通用CRUD方法，不提供分页方法，如需分页请显式使用{@link com.github.pagehelper.PageHelper}。
 * <p>注意事项：
 * <ul>
 *  <li>跨领域优先调用Service而不是MyBatis Mapper/Repository；
 *  <li>谨慎覆盖通用方法，因为有可能造成调用链混乱；
 *  <li>不要使用Wrapper作为入参，其本质上就是Map；
 *  <li>复杂的SQL应该写在XML Mapper方便代码审查；
 *  <li>大数据量场景下，可以选择合理的缓存策略，代替联表查询；
 *  <li>在Service层处理事务，同时避免长事务。
 * </ul>
 *
 * @author Kai
 * @date 2021/6/18 16:44
 **/
public interface BaseService<T extends BaseEntity> {

    /**
     * 返回全部实体
     *
     * <p>出于性能考虑，请谨慎使用此方法。
     *
     * @param fields 返回属性列表（缺省为全部）
     * @return 全部实体类对象
     */
    List<T> list(String... fields);

    /**
     * 将实体类作为查询条件进行查询
     *
     * <p>忽略null值查询条件
     *
     * @param entity 实体类对象
     * @param fields 返回属性列表（缺省为全部）
     * @return 实体列表
     */
    List<T> list(T entity, String... fields);

    /**
     * 根据ID查询实体类
     *
     * @param id 实体ID
     * @return 实体类对象
     */
    @Nullable
    T getById(Long id);

    /**
     * 根据ID查询实体列表
     *
     * @param ids 实体ID列表
     * @return 实体类对象
     */
    List<T> listByIds(Long... ids);

    /**
     * 根据ID查询实体列表
     *
     * @param ids 实体ID集合
     * @return 实体类对象
     */
    List<T> listByIds(Collection<Long> ids);

    /**
     * 将实体类作为查询条件查询一条记录
     *
     * @param entity 实体类对象
     * @return 唯一的实体类对象
     */
    @Nullable
    T getOne(T entity);

    /**
     * 将实体类作为查询条件查询一条记录
     *
     * @param entity  实体类对象
     * @param throwEx 结果集不唯一时是否抛出异常
     * @return 唯一的实体类对象
     */
    @Nullable
    T getOne(T entity, boolean throwEx);

    /**
     * 将实体类作为查询条件进行统计
     *
     * @param entity 实体类对象
     * @return 查询统计结果
     */
    long count(T entity);

    /**
     * 将实体类作为查询条件判断记录是否存在
     *
     * @param entity 实体类对象
     * @return 记录是否存在
     */
    boolean exists(T entity);

    /**
     * 判断实体ID是否已经存在
     *
     * @param id 实体类ID
     * @return 记录是否存在
     */
    boolean exists(Long id);

    /**
     * 持久化实体类
     *
     * <p>null字段将会被忽略
     *
     * @param entity 实体类对象
     * @return 保存结果
     */
    boolean save(T entity);

    /**
     * 批量保存（MySQL专用）
     *
     * <p>注意：null值不会被忽略。
     *
     * @param entities  实体类对象列表
     * @param chunkSize 分批插入，每批数量
     * @return 是否全部保存成功
     */
    boolean saveInBatch(Collection<T> entities, int chunkSize);

    /**
     * 批量保存（MySQL专用）
     *
     * <p>注意：null值不会被忽略。
     * <p>避免长事务，每次批量插入数量不宜过多。
     *
     * @param entities 实体类对象列表
     * @return 是否全部保存成功
     */
    boolean saveInBatch(Collection<T> entities);

    /**
     * 根据ID修改单个实体
     *
     * @param entity 实体类对象
     * @return 修改结果
     */
    boolean modify(T entity);

    /**
     * 根据ID批量修改实体
     *
     * <p>根据每个实体ID批量修改
     *
     * @param entities 实体类对象集合
     * @return 是否全部修改成功
     */
    boolean modifyInBatch(Collection<T> entities);

    /**
     * 新增或修改Entity
     *
     * <p>根据ID新增或修改实体，类似于Upsert。
     *
     * @param entity 实体类对象
     * @return 新增/修改结果
     */
    boolean saveOrModify(T entity);

    /**
     * 根据ID进行删除
     *
     * @param id 实体类ID
     * @return 删除结果
     */
    boolean remove(Long id);

    /**
     * 根据ID列表进行批量删除
     *
     * @param ids 实体类ID列表
     * @return 是否全部删除成功
     */
    boolean removeByIds(Long... ids);

    /**
     * 根据ID列表进行批量删除
     *
     * @param ids 实体类ID集合
     * @return 是否全部删除成功
     */
    boolean removeByIds(Collection<Long> ids);

    /***
     * 批量新增或修改
     *
     * <p>修改范围不可控，不推荐使用此类方法，如确实需要实现，请先经过代码评审！
     *
     * @param entities 实体类对象集合
     */
    default boolean saveOrModifyInBatch(Collection<T> entities) {
        throw new UnsupportedOperationException("不支持的方法");
    }

    /***
     * 唯一性检测（领域内）
     *
     * <p>新增、修改前调用，自动校验带{@link Unique}注解的字段。
     *
     * @param entity 实体类对象
     */
    void checkUnique(T entity);

}
